#pragma once

#include "MBDataTypes.h"
#include "MBEnums.h"

#include <algorithm>

/**
  *	Base class for the available labeling options
  */
class MBLabelingOptions
{
public:
	/**
	  *	Returns an enum value specifying the chosen labeling method
	  *
	  * @return enum value specifying the chosen labeling method
	  */
	const MBLabelingMethod &GetLabelingMethod( void ) const
	{
		return mLabelingMethod;
	}

	/**
	  *	Returns the maximum distance between triangle and camera position
	  *
	  * @return maximum distance between triangle and camera position
	  */
	const double &GetMaximumDistance( void ) const
	{
		return mMaxDistance;
	}

	/**
	  *	Returns the angle weight, which is lambda in the calculation of the
	  * data cost: w_i^j = lambda * sin^2(alpha) + (1 - lambda) * min(1.0, distance / maxDistance)
	  *
	  * @return angle weight
	  */
	const double &GetAngleWeight( void ) const
	{
		return mAngleWeight;
	}

protected:
	/**
	  *	Constructs a new MBLabelingOptions object with the given enum value
	  * specifying the chosen labeling method
	  *
	  * @param labelingMethod enum value specifying the chosen labeling method
	  * @param angleWeight weight of the sin^2(alpha) term in the data cost, must be in [0, 1]
	  * @param maxDistance maximum distance between camera positions and model surface
	  */
	MBLabelingOptions(
		const MBLabelingMethod &labelingMethod,
		const double &angleWeight,
		const double &maxDistance ) :
		mLabelingMethod( labelingMethod )
	{
		mAngleWeight = std::max( 0.0, std::min( 1.0, angleWeight ) );
		
		if (maxDistance <= 0.0)
			mMaxDistance = 1.0;
		else
			mMaxDistance = maxDistance;
	}

private:
	/** Enum value specifying the chosen labeling method */
	MBLabelingMethod mLabelingMethod;
	/** Weight lambda for the calculation of the data cost: w_i^j = lambda * sin^2(alpha) + (1 - lambda) * min(1.0, distance / maxDistance)  */
	double mAngleWeight;
	/** Maximum distance between triangle and camera position */
	double mMaxDistance;
};

/**
  *	An MBLabelingOptionsBestFragment object is an MBLabelingOptions object that
  * corresponds to the labeling method "Best Fragment". In the "Best Fragment"
  * approach every single face of the model is textured by the photo, that is
  * best suited for texturing this face.
  */
class MBLabelingOptionsBestFragment : public MBLabelingOptions
{
public:
	/**
	  *	Constructs a new MBLabelingOptionsBestFragment object
	  *
	  * @param angleWeight weight of the sin^2(alpha) term in the data cost, must be in [0, 1]
	  * @param maxDistance maximum distance between camera positions and model surface
	  */
	MBLabelingOptionsBestFragment(
		const double &angleWeight,
		const double &maxDistance  ) :
		MBLabelingOptions( MB_LABELING_BEST_FRAGMENT, angleWeight, maxDistance )
	{
	}
};

/**
  *	An MBLabelingOptionsGcoUsingMethod object is an MBLabelingOptions object that
  * corresponds to the labeling methods, that are based on the GCO library.
  * This library is used for the energy minimization of a Markov Random Field.
  */
class MBLabelingOptionsGcoUsingMethod : public MBLabelingOptions
{
public:
	/**
	  *	Returns an enum value specifying the chosen labeling method
	  *
	  * @return enum value specifying the chosen labeling method
	  */
	const MBFilteringMode &GetFilteringMode( void ) const
	{
		return mFilteringMode;
	}

	/**
	  *	Returns the number of iterations that are carried out to find an
	  * energy minimum in the Markov Random Field. -1 corresponds to the number
	  * of iterations, that are needed for convergence of the minimum finding
	  * process.
	  *
	  * @return number of iterations that are carried out to find an energy minimum in the Markov Random Field
	  */
	const MBint &GetNumIterations( void ) const
	{
		return mNumIterations;
	}

	/**
	  *	Returns the weight for the smooth cost term. The smooth cost term
	  * calculates the integral of color differences along the seams. Seams
	  * are edges in the model, where faces that are textured by different
	  * photos adjoin each other.
	  *
	  * @return weight for the smooth cost term
	  */
	const double &GetSmoothCostWeight( void ) const
	{
		return mSmoothCostWeight;
	}

	/**
	  *	Returns the cost of every single label
	  *
	  * @return cost of every single label
	  */
	const double &GetLabelCost( void ) const
	{
		return mLabelCost;
	}
protected:
	/**
	  *	Constructs a new MBLabelingOptionsGcoUsingMethod with the given
	  * parameters.
	  *
	  * @param labelingMethod enum value specifying the chosen labeling method
	  * @param angleWeight weight of the sin^2(alpha) term in the data cost, must be in [0, 1]
	  * @param maxDistance maximum distance between camera positions and model surface
	  * @param filteringMode enum value specifying the chosen filtering method
	  * @param numIterations number of iterations that are carried out to find an energy minimum in the Markov Random Field
	  * @param smoothCostWeight weight for the smooth cost term
	  */
	MBLabelingOptionsGcoUsingMethod(
		const MBLabelingMethod &labelingMethod,
		const double &angleWeight,
		const double &maxDistance,
		const MBFilteringMode & filteringMode,
		const MBint &numIterations,
		const double &smoothCostWeight,
		const double &labelCost ) :
	MBLabelingOptions( labelingMethod, angleWeight, maxDistance ),
		mFilteringMode( filteringMode ),
		mNumIterations( numIterations ),
		mSmoothCostWeight( smoothCostWeight ),
		mLabelCost( labelCost )
	{
	}
private:
	/** Enum value specifying the chosen filtering method */
	MBFilteringMode mFilteringMode;
	/** Number of iterations that are carried out to find an energy minimum in the Markov Random Field */
	MBint mNumIterations;
	/** Weight for the smooth cost term */
	double mSmoothCostWeight;
	/** Cost of every single label */
	double mLabelCost;
};

/**
  *	An MBLabelingOptionsMRF object is an MBLabelingOptionsGcoUsingMethod object
  * that corresponds to the labeling method "Markov Random Field". The goal of
  * this approach is the labeling of a model by the energy minimization of a
  * Markov Random Field.
  */
class MBLabelingOptionsMRF : public MBLabelingOptionsGcoUsingMethod
{
public:
	/**
	  *	Constructs a new MBLabelingOptionsMRF object with the given parameters
	  *
	  * @param angleWeight weight of the sin^2(alpha) term in the data cost, must be in [0, 1]
	  * @param maxDistance maximum distance between camera positions and model surface
	  * @param filteringMode enum value specifying the chosen filtering method
	  * @param numIterations number of iterations that are carried out to find an energy minimum in the Markov Random Field
	  * @param smoothCostWeight weight for the smooth cost term
	  */
	MBLabelingOptionsMRF(
		const double &angleWeight,
		const double &maxDistance,
		const MBFilteringMode &filteringMode = MB_FILTERMODE_NEAREST_NEIGHBOR,
		const MBint &numIterations = 2u,
		const double &smoothCostWeight = 50.0,
		const double &labelCost = 0.0 ) :
	MBLabelingOptionsGcoUsingMethod(
		MB_LABELING_MRF,
		angleWeight,
		maxDistance,
		filteringMode,
		numIterations,
		smoothCostWeight,
		labelCost )
	{
	}
};

/**
  *	An MBLabelingOptionsMRFSV object is an MBLabelingOptionsGcoUsingMethod object
  * that corresponds to the labeling method "Markov Random Field with Shift
  * Vectors". The goal of this approach is the labeling of a model by the energy
  * minimization of a Markov Random Field. In contrast to the MRF approach
  * without shift vectors, this approach uses shift vectors in texture space
  * to get rid of the camera registration errors.
  */
class MBLabelingOptionsMRFSV : public MBLabelingOptionsGcoUsingMethod
{
public:
	/**
	  *	Constructs a new MBLabelingOptionsMRFSV object with the given
	  * parameters.
	  *
	  * @param angleWeight weight of the sin^2(alpha) term in the data cost, must be in [0, 1]
	  * @param maxDistance maximum distance between camera positions and model surface
	  * @param filteringMode enum value specifying the chosen filtering method
	  * @param numIterations number of iterations that are carried out to find an energy minimum in the Markov Random Field
	  * @param smoothCostWeight weight for the smooth cost term
	  * @param numPyramidLevels number of image pyramid levels that are involved in the energy minimization process
	  * @param numTopmostLabelsToConsider number of best labels for every single face that are considered for the next iteration
	  */
	MBLabelingOptionsMRFSV(
		const double &angleWeight,
		const double &maxDistance,
		const MBFilteringMode & filteringMode = MB_FILTERMODE_NEAREST_NEIGHBOR,
		const MBint &numIterations = 2u,
		const double &smoothCostWeight = 50.0,
		const double &labelCost = 0.0,
		const MBuint &numPyramidLevels = 6u,
		const MBuint &numTopmostLabelsToConsider = 1u ) :
	MBLabelingOptionsGcoUsingMethod(
		MB_LABELING_MRF_WITH_SHIFTING_VECTORS,
		angleWeight,
		maxDistance,
		filteringMode,
		numIterations,
		smoothCostWeight,
		labelCost ),
		mNumPyramidLevels( numPyramidLevels ),
		mNumTopmostLabelsToConsider( numTopmostLabelsToConsider )
	{
	}

	/**
	  *	Returns the number of image pyramid levels that are involved in the
	  * energy minimization process
	  *
	  * @return number of image pyramid levels that are involved in the energy minimization process
	  */
	const MBuint &GetNumPyramidLevels( void ) const
	{
		return mNumPyramidLevels;
	}

	/**
	  *	Returns number of best labels for every single face that are considered for the next iteration
	  *
	  * @return number of best labels for every single face that are considered for the next iteration
	  */
	const MBuint &GetNumTopmostLabelsToConsider( void ) const
	{
		return mNumTopmostLabelsToConsider;
	}
private:
	/** Number of image pyramid levels that are involved in the energy minimization process */
	MBuint mNumPyramidLevels;
	/** Number of best labels for every single face that are considered for the next iteration */
	MBuint mNumTopmostLabelsToConsider;
};